初來乍到Ruby世界的讀者們,絕對想不到原來Ruby 也有 curry, bind 等用法。這些語法對於JS的使用者應該很熟悉,在今年的IT鐵人賽就看到很多介紹bind, curry
Closure是程式語言中很基本的概念,首先要先講Proc和 closure 的關係

在Ruby的Block則是用{}或do ... end 來做領域的展開,領域裡和領域外是完全不同的世界。外面的值沒有辦法干預到裡面,而裡頭的數值和運算也不會影響到外界。這就是closure的概念,我們稱為閉包。
沒有術式就不會有領域,而以 Ruby 來說,一共有下列幾種術式能夠展開領域。
#====== Proc
proc {}
Proc.new {}
#====== Proc(Lambda)
lambda {}
-> {}
#====== map, each...
[1, 2, 3].each {}
我們也可以自己做術式,而我們使用的術式為yield。
block_given? 為yield 的好朋友,所以我們先介紹block_given?
def apple_machine
block_given? && 'block_used' || 'block_unused'
end
apple_machine #=> block_unused
apple_machine {} #=> block_used
由上面的例子我們可以知道, block_given? 可以偵測apple_machine是否使用block, block_given? 可以讓我們在有block或沒有block的情況下做應對,接著我們開始講yield。
# 情境1
def apple_machine
yield if block_given?
end
apple_machine { 1 } #=> 1
apple_machine { 1 + 1 } #=> 2
apple_machine { [1, 2, 3].sum } #=> 6
# 情境2
def washing_machine
yield if block_given?
2
end
apple_machine { 1 } #=> 2
apple_machine { 1 + 1 } #=> 2
apple_machine { [1, 2, 3].sum } #=> 2
yield字面上的意思為讓路。上述的情境1、情境2,想要傳達給讀者的訊息為不管是簡單的1、稍微複雜的1+1,或者更複雜的[1, 2, 3].sum。在進入block以後,程式碼的會先被Block拿走主控權,結束block以後再回歸原來的程式。
# 情境3
def itday9_method(num = 0)
yield(num + 1) if block_given?
end
itday9_method #=> nil
itday9_method(2) { |n| n*100 } #=> 300
[1, 2, 3].map { |n| itday9_method(n) { |p| p*100 } } #=> [200, 300, 400]
我們可以利用yield將方法裡頭的值傳出去給外面的Block,給外面的Block計算以後再回傳運算的結果回來。情境3的用法很重要
實際的狀況下,我們傳的不會只是num+1,可以是綠界回傳的付款結果、貨運單狀態、POS錯誤訊息等等。
yield或許不是個好寫法,因為使用太多的Block會造成維護上的不易讀性,不過個人在專案上用的地方還蠻多的。使用在只有自己在維護的專案裡面,寫了Block就可以少想很多設計流程。
以下有幾個例子,可以正大光明在Rails使用Block。
view:如視窗畫面、卡片、頁籤layout :搭配content_for,如使用Action Mailer
decorator pattern,可以用yield實現 ➡️ Day32 提到攤提Bind 其實是 React 使用 class component 比較常見的寫法,但 bind 不是只能綁定 this,還可以綁任何物件。下面的例子為,javascript 的bind 將裡面的this 與外面的[1, 2, 3] 綁在一起。
function Point() {
return [...this, 4, 5, 6]
}
point = Point.bind([1, 2, 3])
point()
// [1, 2, 3, 4, 5, 6]
bind是一種可以和外部世界聯絡的橋樑,而這個方法在Ruby 也有。
a = 123 # 外部變數
block = proc { a } # Procedure
block.binding.local_variable_get(:a) # 綁定外部的變數
block.call #=> 123
Curry是Javascript對function的一種進階用法,在JS中也是漢漢老師常用的寫法。此外,我也常常在面試中看到使用Curry的題目
/* 公司後輩的面試題目 */
function adder(a) {
/* function 可以當作變數,因此我們可以將 function 回傳出去。
意即為: function return 出來的結果還是 function */
return function(b) {
return a + b
}
}
/* 公司專案常見的寫法,寫法等同於上方 */
const adder = a => b => (a + b)
/* 範例1: a = 1, b = 2 */
adder(1)(2) // 3
/* 範例2: 用在map */
[1, 2, 3].map(e => adder(1)(e)) // [2, 3, 4]
範例1用了兩個括號把值回傳出來,範例2為使用map 這個 Higher Order Function,我們在使用Higher Order Function時,裡面的adder必須也是function。利用curry的特性,做一個匿名函數 e => adder(1)(e)提供給map使用。
curry絕對不是炫技用,而是為了做到把function可以變成變數的特性而衍生出的技巧。以下為漢漢老師實際上使用的curry實例
/* 實際應用上使用的 curry (看不懂沒關係) */
export const ajaxReload = (table) => () => table.api().ajax.reload();
export const multiAjaxReload = (tables) => () => tables.forEach(table => table.api().ajax.reload())
export const dataWithStatus = (selectedValue, type = null) => e => ({...e, [checkedOrSelected(type)]: is_in(e.value, selectedValue)})
Ruby 也有一樣的方法,我們來看以下例子
divisible_by = ->(x,y) { (y % x).zero? }.curry
(1..10).select(&divisible_by.call(5)) #=> [5, 10]
(1..10).select(&divisible_by.call(2)) #=> [2, 4, 6, 8, 10]
我們分別傳5, 2 給divisible_by 這個物件對,接著select會使用divisible_by.call作為篩選。至於 &符號,我們會在Day10解釋
Day10 我們會介紹&,以及詳細說明yield的運作原理!